package org.hagopa.games.moneypool.game;

import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;

import org.hagopa.games.moneypool.dblayer.GameGeneralInfoAdopt;
import org.hagopa.games.moneypool.msgs.CommandsEnum;
import org.hagopa.games.moneypool.msgs.Message;
import org.hagopa.games.moneypool.msgs.ContentTypes.ChatMsg;
import org.hagopa.games.moneypool.network.ClientStatus;
import org.hagopa.games.moneypool.network.NetClientHandler;
import org.hagopa.games.moneypool.utils.CommonTracer;


public class GameThread implements Runnable{
    
	private long index = 0;
	private long gameId = 0;
	private int  currPlayerIndex = 1;
	//private DiesPair localDies = new DiesPair();
	private long default_timeout = 50;
	private Selector sel = null;
	private boolean isRunning = false;
        private CommonTracer cLogger = CommonTracer.getInstance();
	private ConcurrentHashMap<Integer, PlayerHolder> waitingPlayers = new ConcurrentHashMap<Integer, PlayerHolder>();
	private ConcurrentLinkedQueue <PlayerHolder> newPlayers = new ConcurrentLinkedQueue <PlayerHolder>();
	private GameProcedure GP = null;
	private GameGeneralInfoAdopt gdata = null;
        
	private CopyOnWriteArrayList<Integer> RemovedPlayers = new CopyOnWriteArrayList<Integer>();
	
	
	public GameThread(long id, GameGeneralInfoAdopt gId){
		this.index = id;
		this.gdata = gId;
		System.out.println("[GameThread](GameThread) - Game index:" + this.index);
		
		
		this.GP =  new GameProcedure(this.gdata);
		GP.init();
		GP.setupPlayersList(this.waitingPlayers);
		try {
			sel = Selector.open();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
        @Override
	public void run() 
	{
		Thread.currentThread().setName("BogoGameThread"+gameId);
                System.out.println("[GameThread](GameThread) - Thread Id: " + Thread.currentThread().getId());
		System.out.println("[GameThread](GameThread) - thread: " +System.identityHashCode(Thread.currentThread()));
		System.out.println("[GameThread](run) - New Game Running!!!! Id:" + gameId + " index: " + index);
		this.isRunning = true;
		mainLoop();
	}
	
	public void stop(long timeout)
	{
		try {
			Thread.sleep(timeout);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.isRunning = false;
	}
	public void mainLoop()
	{
		System.out.println("[GameThread](mainLoop) - th["+ Thread.currentThread().getId()+ "] GameThread MainLoop");
		
		while(this.isRunning)
		{
			int num = 0;
			int counter = 0;
			if(!newPlayers.isEmpty())
			{
				registerPlayer(newPlayers.remove());
			}
			try {
				num = sel.select(default_timeout);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			if(num == 0)
				continue;
			
			Set<SelectionKey> keys = Collections.synchronizedSet(sel.selectedKeys());
			
			
			Iterator<SelectionKey> it = keys.iterator();
			while(it.hasNext())
			{
				SelectionKey skey = (SelectionKey)it.next();
				it.remove();
				if (skey.isValid() && skey.isReadable())
				{
					if(this.waitingPlayers.isEmpty())
						return;
					
					Set<Integer> players = this.waitingPlayers.keySet();
					
					Iterator<Integer> ph = players.iterator();
					while(ph.hasNext())
					{
						counter++;
						PlayerHolder tmp = this.waitingPlayers.get(ph.next());
						if(((NetClientHandler)tmp.getClientHandler()).getChannel().isConnected()
								&& ((NetClientHandler)tmp.getClientHandler()).getChannel().socket().isConnected())
						{
							if(skey.channel().equals(((NetClientHandler)tmp.getClientHandler()).getChannel()))
							{
								System.out.println("[GameThread](mainLoop) - th["+ Thread.currentThread().getId()+ "] Channel Found");
								if(null != ((NetClientHandler)tmp.getClientHandler()).getMsg())
								{
									responseToClient(tmp);
								}
								else
								{
									System.out.println("[GameThread](mainLoop)- th["+ Thread.currentThread().getId()+ "]  Player No " +tmp.getGId() + " disconnected");
									skey.cancel();
									try {
										skey.channel().close();
										} catch (IOException e) {
											// TODO Auto-generated catch block
											e.printStackTrace();
										}
									removePlayer(tmp);
								}
							}
						}
						else
						{
							System.out.println("[GameThread](mainLoop) - th["+ Thread.currentThread().getId()+ "]Socket is not Connected!!!!!");
							skey.cancel();
							try {
								skey.channel().close();
								} catch (IOException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
						}
					}//while(ph.hasNext())
				
				}
				else
				{
					System.out.println("[GameThread](mainLoop) - Key is not Connectable!!!!!");
					skey.cancel();
					
				}
				
				if(counter == 0)
					this.isRunning = false;
			}
			
				keys.clear();
			try {
				Thread.sleep(this.default_timeout);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
			
		}
	}
	
	private void responseToClient(PlayerHolder newPlayer)
	{
		
		if(null == newPlayer)
		{
			System.out.println("[GameThread](responseToClient) - th["+ Thread.currentThread().getId()+ "]Playerholder is Empty");
			return;
		}
		Message tmp = ((NetClientHandler)newPlayer.getClientHandler()).getLastClientMsg();
		if(tmp == null)
		{
			System.out.println("[GameThread](responseToClient) - Failed to get Message");
			return;
		}
		
		System.out.println("[GameThread](responseToClient) - th["+ Thread.currentThread().getId()+ "]Last Command:" + tmp.getCommand().idx());
		
		 if(CommandsEnum.CHAT_MSG.idx() == tmp.getCommand().idx())
		 {
			Message reply = new Message();
			
			reply.setSessionId(tmp.getSessionId());
			reply.setCommand(CommandsEnum.CHAT_MSG);
		
			reply.setContentSz(tmp.getContentSz());
			reply.setContentHandler(((ChatMsg)tmp.getContentHandler()));
                        String logmsg = "CHATMSG:" + ((ChatMsg)tmp.getContentHandler()).getChatMsg();
                        cLogger.storeLogmsg(1, this.gdata.getGame_id(), tmp.getClientId(), 0 , logmsg);
                        if(((ChatMsg)tmp.getContentHandler()).getRcptId() == 0)
                        {
                            reply.setClientId(tmp.getClientId());
                            broadcastChatMsg(reply);
                        }
                        else
                        {
                            reply.setClientId(((ChatMsg)tmp.getContentHandler()).getRcptId());
                            PlayerHolder target = this.waitingPlayers.get(new Integer(((ChatMsg)tmp.getContentHandler()).getRcptId()));

                            ((NetClientHandler)target.getClientHandler()).setMsg4Client(reply);
                            ((NetClientHandler)target.getClientHandler()).sendMsg(reply.marshall());
                        }
			System.out.println("[GameThread](responseToClient) - th["+ Thread.currentThread().getId()+ "] Sent chat message broadcast!!!!!!!!!");
		 }
		 else
		 {
			 this.GP.getPlayerAction(newPlayer);
			 
		 }
		 Thread.yield();
	}
	
	
	private  synchronized void registerPlayer(PlayerHolder newPlayer)
	{
		((NetClientHandler)newPlayer.getClientHandler()).setStatus(ClientStatus.PLAYING);
		if(newPlayer.isNetClient())
		{
			System.out.println("[GameThread](registerPlayer) - trying to register to selector");
			try 
			{
				((NetClientHandler)newPlayer.getClientHandler()).response();
				System.out.println("[GameThread](registerPlayer) - after response");
				((NetClientHandler)newPlayer.getClientHandler()).getChannel().register(sel, SelectionKey.OP_READ);
				//((NetClientHandler)newPlayer.getClientHandler()).getChannel().register(sel, SelectionKey.OP_WRITE);
				((NetClientHandler)newPlayer.getClientHandler()).setWhereRegistered("GameThreadId:"+ Thread.currentThread().getId());
				this.waitingPlayers.put(new Integer(currPlayerIndex), newPlayer);
				currPlayerIndex++;
				
			}
			catch (ClosedChannelException e) 
			{
				e.printStackTrace();
			}
		}
		else{
			System.out.println("[GameThread](appendPlayerToGame) - Not Connected, or not Network Client");
		}
	}
	public  void appendPlayerToGame(PlayerHolder newPlayer)
	{
		// Player starts with 500 tugriks of Cash
		//if player type is active (1) we append him to game, and give him game seq number
		// If it is a network client and not AIPlayer we send response (for a while)
		
		int startCash = this.gdata.getStart_payment();
		if(null == newPlayer)
		{
			System.out.println("[GameThread](appendPlayerToGame)th["+ Thread.currentThread().getId()+ "] - Empty Player Holder");
			return;
		}
		System.out.println("[GameThread](appendPlayerToGame) - th["+ Thread.currentThread().getId()+ "] PLayers counter: " + currPlayerIndex);
		if(newPlayer.getType() == 1)
		{
			
			if(currPlayerIndex > 6)
				return;
			newPlayer.setGId(currPlayerIndex);
			
			newPlayer.setCash(startCash);
						
			//newPlayer.setGId(currPlayerIndex);
			
			System.out.println("[GameThread](appendPlayerToGame) - th["+ Thread.currentThread().getId()+ "]  To Game Id: " + ((NetClientHandler)newPlayer.getClientHandler()).getGameId());
			
			newPlayers.add(newPlayer);
						
			//this.waitingPlayers.put(new Integer(currPlayerIndex), newPlayer);
			
				//sendStartTurn();
				
				
			
			
		}
	}
	private void removePlayer(PlayerHolder player)
	{
		System.out.println("[GameThread](removePlayer) -  Remove Player: " + player.getGId() + "client Id: " + ((NetClientHandler)player.getClientHandler()).getClientId());
		RemovedPlayers.add(new Integer(((NetClientHandler)player.getClientHandler()).getClientId()));
		
		if(RemovedPlayers.size() == this.gdata.getMax_players())
                {
                    System.out.println("[GameThread](removePlayer) -Removed last activ e player. Drop game thread");
			this.isRunning = false;
                }
	}
	public void broadcastChatMsg(Message msg)
	{
		System.out.println("[GameThread](broadcastChatMsg) - BroadCasting!!");
		Set<Integer> players = this.waitingPlayers.keySet();
		System.out.println("[GameThread](broadcastChatMsg) - keys num:" + players.size());
		Iterator<Integer> pl = players.iterator();
		
		System.out.println("[GameThread](broadcastChatMsg) -  Total players:" + this.waitingPlayers.size());
		int senderId = msg.getClientId();
		
		while(pl.hasNext())
		{
			Integer ind = pl.next();
			System.out.println("[GameThread](broadcastChatMsg) - Index: " + ind);
			PlayerHolder tmp = this.waitingPlayers.get(ind);
			System.out.println("[GameThread](broadcastChatMsg) - Msg Owner Id:" + senderId);
			System.out.println("[GameThread](broadcastChatMsg) Check now player id:" + ((NetClientHandler)tmp.getClientHandler()).getClientId());
			if(((NetClientHandler)tmp.getClientHandler()).getClientId() != senderId)
			{
				Message nmsg = msg;
				
				nmsg.setClientId(((NetClientHandler)tmp.getClientHandler()).getClientId());
				System.out.println("[GameThread](broadcastChatMsg) -  Sending to client Id:" + nmsg.getClientId());
				nmsg.setSessionId(((NetClientHandler)tmp.getClientHandler()).getLastClientMsg().getSessionId());
				System.out.println("[GameThread](broadcastChatMsg) -  Sending to player:" + ((NetClientHandler)tmp.getClientHandler()).getClientId());
				
				((NetClientHandler)tmp.getClientHandler()).sendMsg(nmsg.marshall());
			}
			
		}
	}
	
	
	public boolean isRunning() {
		return isRunning;
	}
	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	public long getGameId() {
		return gameId;
	}

	public void setGameId(long gameId) {
		this.gameId = gameId;
	}

	public int getCurrPlayerIndex() {
		return currPlayerIndex;
	}
	
}
